from twisted.internet import reactor, protocol
from twisted.protocols import basic

import carddeck, cardhand, carddealer, cardplayer
import copy
import cPickle
import message
import sched, time
from threading import Thread, Event, enumerate
import signal

import MySQLdb as mdb
from passlib.handlers.django import django_pbkdf2_sha256 as handler
from common.cred import Credentials

# NEVER USE sendLine() outside of the sendOne() function!

class Game:
    gameStartDelay = 5
    handDelay = 5
    turnDelay = 5
    moreDelay = 0

    currentPlayer = 0
    howManyPlayers = 0
    gameStarted = False
    changedPlayers = []

    continueLoopingEvent = Event()
    def __init__(self, chatFactory):
        self.continueLoopingEvent.set()

        self.factory = chatFactory
        self.deck = carddeck.Deck(carddeck.DECKSIZE)
        self.deck.shuffle()

        self.dealer = carddealer.Dealer()
        self.dealer.new_hand(self.deck.deal_card(), self.deck.deal_card())

        self.players = [None] * 5
        self.players.append(self.dealer)

    def AddPlayer(self, name, bank):
        self.changedPlayers.append((True, name, bank))

    def RemovePlayer(self, name):
        self.changedPlayers.append((False, name))

    def UpdatePlayers(self):
        # player
        # (True,playerName) == Add
        # (False,playerName) == Delete
        for player in self.changedPlayers:
            if player[0]:
                for i in range(0, 5):
                    if not self.players[i]:
                        self.players[i] = cardplayer.Player(player[1])
                        break
            elif player[0] == False:
                for i in range(0, 5):
                    if self.players[i]:
                        if self.players[i].name == player[1]:
                            self.players[i] = None
                            break
            else:
                for i in range(0, 5):
                    if self.players[i]:
                        if self.players[i].name == player[1]:
                            self.players[i].current_bet = player[2]
        self.changedPlayers = []
        if len(self.players) == 0:
            self.gameStarted = False
            self.continueLoopingEvent.clear()

    def SetCurrentPlayer(self):
#        print 'Attempting to SetCurrentPlayer'
        count = 0
        for player in self.players:
            if player:
                if player.myTurn:
                    break
            count += 1
#        print "count after who's turn loop: " + str(count)

        if count >= 5:
            count = 0
            for player in self.players:
                    if player:
                        player.myTurn = True
                        self.currentPlayer = count
                        break
                    count += 1
            if count == 5:
                self.players[5].myTurn = False
        else:
            self.players[count].myTurn = False
            for i in range(count + 1, 6):
                if self.players[i]:
                    self.players[i].myTurn = True
                    self.currentPlayer = i
                    break
#        print 'Current player = ' + str(self.currentPlayer)
#        print 'Exiting SetCurrentPlayer()'

    def StartGame(self):
        print 'Attempting to Start Game'
        if not self.gameStarted:
            self.gameStarted = True
            if len(self.factory.clients) != 0:
                self.factory.clients[0].sendTimer(self.gameStartDelay)
            time.sleep(self.gameStartDelay)
            self.StartHand()
#            print "Exiting StartGame()"
            return 'Hand beginning in ' + str(self.handDelay) + ' seconds.'
#        else:
#            print "Exiting StartGame()"
#            return 'Game in progress.'

    def StartHand(self):
        while True:
            self.UpdatePlayers()
            if self.gameStarted:
                self.SetCurrentPlayer()

#                print 'Attempting to Start Hand'
                for player in self.players:
                    if player:
                        player.new_hand(self.deck.deal_card(), self.deck.deal_card())

#                for player in self.players:
#                    if player:
#                        print player.name + ": " + str(player)
                print str(len(self.factory.clients)) + ' client(s) currently connected.'

                if len(self.factory.clients) != 0:
                    self.factory.clients[0].sendPlayerStates()

                    for player in range(0, 5):
                        if self.players[player]:
                            self.currentPlayer = player
                            self.players[player].myTurn = True
                            if len(self.factory.clients) != 0:
                                self.factory.clients[0].sendPlayerStates(False, self.turnDelay)
                            self.StartTurn()
                            self.players[player].myTurn = False
                            if len(self.factory.clients) != 0:
                                self.factory.clients[0].sendPlayerStates(False, self.turnDelay)
                            self.moreDelay = 0
                    self.DealersTurn()
                    self.WriteToDB()
                    for player in self.players:
                        if player:
                            player.reset()

                    if len(self.factory.clients) != 0:
                        self.factory.clients[0].sendTimer(self.handDelay + 1)
                        time.sleep(self.handDelay)
                else:
                    self.gameStarted = False
                    self.continueLoopingEvent.clear()
                    self.continueLoopingEvent.wait()
#                print 'Exiting StartHand()'
            else:
                break

    def StartTurn(self):
#        print 'Attempting to Start Turn'
        self.continueLoopingEvent.clear()
        if self.currentPlayer != 5 and len(self.factory.clients) != 0:
            self.factory.clients[0].sendTimer(self.turnDelay)
            self.continueLoopingEvent.wait(self.turnDelay + 1)
            while self.moreDelay > 0 and len(self.factory.clients) != 0:
                self.factory.clients[0].sendTimer(self.moreDelay * 2)
                self.continueLoopingEvent.wait(2) # Incremented each time the player hits.
                self.moreDelay -= 2
            self.continueLoopingEvent.wait(1)

#        print self.players[self.currentPlayer].name + "'s turn"
#        print 'Exiting StartTurn()'

#        self.EndTurn()

#    def EndTurn(self):
#        print 'Attempting to End Turn'
#        self.players[self.currentPlayer].myTurn = False
#        if self.currentPlayer == 5:
#            self.DealersTurn()
#        print 'Exiting Game.EndTurn()'

    def DealersTurn(self):
        self.players[5].myTurn = True
        if len(self.factory.clients) != 0:
            # This displays the Dealer's down card.
            self.factory.clients[0].sendPlayerStates(True)
        if self.players[5].total <= 16:
            sentinel = True
            while sentinel:
                sentinel = self.players[5].hit(self.deck.deal_card())
#                print 'Hit: Dealer'
        if len(self.factory.clients) != 0:
            self.factory.clients[0].sendPlayerStates(True)
        self.players[5].myTurn = True
#        else:
#            print "Dealer's hand totals: " + str(self.players[5]._get_total())

    def Hit(self, name):
#        print 'Attempting to Hit'
        for i in range(0, 7):
            if name == self.players[i].name:
                break
        if i < 5:
            if self.players[i].myTurn:
                if self.players[i]._get_total() < 21:
                    self.players[i].hit(self.deck.deal_card())
#                    print 'Hit: ' + name
                    self.moreDelay += 1
                    return True
                else:
                    self.Stand(name)
#        print 'Exiting Game.Hit()'

    def Stand(self, name):
#        print 'Attempting to Stand'
        for i in range(0, 7):
            if name == self.players[i].name:
                break
        if i != 6:
                if self.players[i].myTurn:
                    self.continueLoopingEvent.set()
#                    print name + ' stands.'
#        else:
#            print name + "'s stand failed."
#        print 'Exiting Game.Stand()'

    def Bet(self, name, amount):
#        print 'Attempting to change bet'
        for i in range(0, 7):
            if name == self.players[i].name:
                break
        if i < 5:
            self.changedPlayers.append((None, name, amount))
#            self.players[i].current_bet = amount
#        else:
#            print name + "'s stand failed."
#        print 'Exiting Game.Bet()'

    def WriteToDB(self):
        pass

def t():
    return "[" + time.strftime("%H:%M:%S") + "] "

class EchoProtocol(basic.LineReceiver):
    def __init__(self):
        self.name = "Unnamed"
        self.lineBuffer = None

    # Once a local list of Players is implemented use this to update the client(s) current state.
    # def updateNames(self):
        # sendMsg(players)

    def connectionMade(self):
        self.sendOne("chat", "Welcome, what is your name?")
        # self.sendOne("chat", "")
        self.count = 0
        self.factory.clients.append(self)
        print t() + "+ Connection from: " + self.transport.getPeer().host

    def connectionLost(self, reason):
        if self.name != 'Unnamed':
            self.sendMsg("chat", "- %s left." % self.name)
        print t() + "- Connection lost: " + self.name
        self.factory.clients.remove(self)
        self.factory.game.RemovePlayer(self.name)


    def lineReceived(self, line):
        lines = line.rstrip("\t\r\n").split("\t")
        # lines.pop() # Last item in the list will always be an empty string since all lines are \t terminated
        if self.lineBuffer:
            lines[0] = self.lineBuffer + lines[0]
            self.lineBuffer = None
        for stripMe in lines:
                item = stripMe.strip("\r\n")
                try:
                    undata = cPickle.loads(item)
                    if isinstance(undata, message.Message):
#                        print type(undata)
                        if undata.type == "chat":
                            if undata.payload == 'quit':
                                self.sendOne("chat", "Goodbye.")
                                self.transport.loseConnection()
                                return
                            elif undata.payload == "userlist":
                                self.chatters()
                                return
                            self.sendMsg("chat", "<" + self.name + ">: " + undata.payload)
                            return
                        elif undata.type == "auth":
                            self.username(undata.payload)
                        elif undata.type == "hit":
                            if self.factory.game.Hit(self.name):
                                self.sendPlayerStates()
                        elif undata.type == "stand":
                            self.factory.game.Stand(self.name)
                        elif undata.type == "bet":
                            self.factory.game.Bet(self.name, undata.payload)
                        elif undata.type == "quit":
                            pass
                except (cPickle.UnpicklingError, EOFError) as e:
                    self.lineBuffer = stripMe
                    print type(e)

    def username(self, credentials):
        con = None

        username = credentials.username_ascii
        password = credentials.password_ascii

        for x in self.factory.clients:
            if x.name == username:
                self.sendOne("auth", "fail:this username is already in use")
                return

        if username == 'Unnamed':
            self.sendOne("auth", "fail:this username is already in use")
            return

        authorized = False
        try:
            con = mdb.connect(host='localhost', user='root', passwd='root', db='netjack')
            cur = con.cursor(mdb.cursors.DictCursor)

            cur.execute("SELECT * FROM auth_user WHERE username=%s", (credentials.username))
            row = cur.fetchone()
            # GET iterations, salt, hash
            if row:
                db_hash = row['password']
            else:
                self.sendOne('auth', 'fail:username or password incorrect')
                return

            if db_hash != None:
                authenticated = handler.verify(password, db_hash)
                if authenticated:
                    user_id = row['id']
                    cur.execute("SELECT balance FROM bank WHERE user_id=%s", (user_id))
                    row = cur.fetchone()
                    bank = row['balance']
                    msg = ('auth', 'okay:' + username)
                    authorized = True
            cur.close()
            con.close()
        except Exception, ex:
            print ex
        if not authorized:
            msg = ('auth', 'fail:username or password incorrect')
            self.sendOne(msg[0], msg[1])
            return

        self.sendOne(msg[0], msg[1])

        self.name = username

        self.chatters()
        self.sendOne("chat", "Chat away!")
        self.count += 1
        self.sendOne("chat", "+ %s joined.\n" % self.name)

        self.factory.game.AddPlayer(self.name, bank)
        if not self.factory.game.gameStarted:
            if self.factory.gameThread:
                self.factory.game.gameStarted = True
                self.factory.game.continueLoopingEvent.set()
            else:
                self.sendMsg('chat', 'Beginning game.')
                self.factory.gameThread = Thread(target=self.factory.game.StartGame)
                self.factory.gameThread.start()
                print 'Living threads:'
                print repr(enumerate())

        print '%s~ %s is now known as %s\n' % (t(), self.transport.getPeer().host, self.name)

    def chatters(self):
        x = len(self.factory.clients)
        s = 'is' if x == 1 else 'are'
        p = 'person' if x == 1 else 'people'
        self.sendOne("chat", "There %s %i %s connected:" % (s, x, p))

        for client in self.factory.clients:
            # if client is not self:
            self.sendOne("chat", client.name)
        # self.sendOne("chat", "")

    def sendPlayerStates(self, showDealearHand=False, timer=0):
        if showDealearHand:
            self.sendMsg("userStates", self.factory.game.players, timer)
        else:
            temp = copy.deepcopy(self.factory.game.players)
            temp[5].new_hand(None, temp[5].current_hand[1])
            self.sendMsg("userStates", temp, timer)
            del temp

    def sendTimer(self, timer=0):
        self.sendMsg("timer", timer)

    # Use this to send a message to all connected clients
    def sendMsg(self, messageType, msg, timer=0):
        for client in self.factory.clients:
            client.sendLine(cPickle.dumps(message.Message(messageType, msg)) + "\t")

    # Use this to communicate with a single client
    def sendOne(self, messageType, msg):
        self.sendLine(cPickle.dumps(message.Message(messageType, msg)) + "\t")

class EchoServerFactory(protocol.ServerFactory):
        def startFactory(self):
            self.game = Game(self)
            self.protocol = EchoProtocol
            self.clients = []
            self.gameThread = None

        def stopFactory(self):
            print "Shutting down Factory."
            while self.gameThread.is_alive():
#                print 'Trying to force thread to return.'
                self.game.gameStarted = False
                self.game.continueLoopingEvent.set()
            print 'Living threads:'
            print repr(enumerate())
            print "Exiting stopFactory()"


def onClose(signal, frame):
    print 'Caught ctrl-c'
    reactor.stop()

if __name__ == "__main__":
    signal.signal(signal.SIGINT, onClose,)
    reactor.listenTCP(5001, EchoServerFactory())
    reactor.run()
